use anyhow::Result;
use axum::extract::Request;
use axum::routing::get;
use fmt::Layer;
use opentelemetry::global;
use opentelemetry::trace::TracerProvider;
use opentelemetry_otlp::{SpanExporter, WithExportConfig};
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use std::sync::OnceLock;
use std::time::Duration;
use tokio::time::sleep;
use tracing::metadata::LevelFilter;
use tracing::{info, instrument, span};
use tracing_subscriber::fmt;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;

#[tokio::main]
async fn main() -> Result<()> {
    let tracer_provider = init_traces();
    // Set the global tracer provider using a clone of the tracer_provider.
    // Setting global tracer provider is required if other parts of the application
    // uses global::tracer() or global::tracer_with_version() to get a tracer.
    // Cloning simply creates a new reference to the same tracer provider. It is
    // important to hold on to the tracer_provider here, so as to invoke
    // shutdown on it when application ends.
    global::set_tracer_provider(tracer_provider.clone());

    let file_appender = tracing_appender::rolling::daily("tmp/log", "ecosystem.log");
    let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);

    let console = Layer::new()
        .with_ansi(true)
        .with_line_number(true)
        .with_target(true)
        .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
        .with_thread_ids(true) // include the thread ID of the current thread
        .with_thread_names(true) // include the name of the current thread
        .json();

    let file = Layer::new()
        .with_ansi(true)
        .with_line_number(true)
        .with_target(true)
        .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
        .with_thread_ids(true) // include the thread ID of the current thread
        .with_thread_names(true)
        .with_writer(non_blocking) // include the name of the current thread
        .json();

    let tracer = tracer_provider.tracer("readme_example");
    let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer);

    tracing_subscriber::registry()
        .with(LevelFilter::INFO)
        .with(console)
        .with(file)
        .with(opentelemetry)
        .init();

    let address = "127.0.0.1:9001";
    let app = axum::Router::new()
        .route("/", get(index_handler))
        .route("/long-task", get(long_task));
    let listener = tokio::net::TcpListener::bind(address).await?;
    info!("starting server on {}", address);
    axum::serve(listener, app.into_make_service()).await?;
    Ok(())
}

fn get_resource() -> Resource {
    static RESOURCE: OnceLock<Resource> = OnceLock::new();
    RESOURCE
        .get_or_init(|| Resource::builder().with_service_name("axum").build())
        .clone()
}

fn init_traces() -> SdkTracerProvider {
    let exporter = SpanExporter::builder()
        .with_tonic()
        .with_endpoint("http://192.168.6.90:4317")
        .build()
        .expect("Failed to create trace exporter");

    SdkTracerProvider::builder()
        .with_batch_exporter(exporter)
        .with_max_attributes_per_span(64)
        .with_max_events_per_span(32)
        .with_resource(get_resource())
        .build()
}

#[instrument(fields(http.uri = req.uri().path(),http.method = req.method().as_str()))]
async fn index_handler(req: Request) -> &'static str {
    sleep(Duration::from_millis(111)).await;
    long_task().await;
    span!(tracing_core::metadata::Level::INFO, "my span");
    info!(http.status_code = 200, "my span request");
    "hello world"
}

#[instrument]
async fn long_task() -> &'static str {
    sleep(Duration::from_millis(500)).await;
    "hello long task"
}
